package org.orbroker

import org.orbroker.util.Mirror._
import org.orbroker.exception._
import org.orbroker.conv._
import org.orbroker.adapt.BrokerAdapter
import org.orbroker.callback.ExecutionCallback
import java.sql.{ SQLException, ResultSet, Connection, PreparedStatement, CallableStatement }

import java.io.Reader
import java.util.{ Map ⇒ JMap, HashMap ⇒ JHashMap }
import scala.util.matching._

private[orbroker] abstract class SQLStatement(
    val id: Symbol,
    protected val callback: ExecutionCallback,
    protected val adapter: BrokerAdapter) {

  import SQLStatement._

  protected def statement(parms: Map[String, Any]): ParsedSQL

  protected final def setParms(token: Token[_], ps: PreparedStatement, parmDefs: Seq[String], parms: Map[String, _]): Seq[Any] = {
    val values = new scala.Array[Any](parmDefs.size)
    var idx = 0
    for (parmDef ← parmDefs) {
      values(idx) = setParm(token, ps, idx + 1, parmDef, parms)
      idx += 1
    }
    values
  }

  protected final def setParm(token: Token[_], ps: PreparedStatement, parmIdx: Int, parmDef: String, parms: Map[String, _]): Any = {
    var parm: Any = null
    try {
      parm = token.convert(leafValue(parms, parmDef))
      adapter.setParameter(token.id, ps, parm, parmIdx)
      parm
    } catch {
      case e ⇒ {
        val parmType = if (parm == null) {
          parm = "NULL"
          "NULL"
        } else {
          parm.asInstanceOf[AnyRef].getClass.getName
        }
        val msg = "Statement '%s' could not set parameter :%s with value %s and type '%s'".format(id.name, parmDef, parm, parmType)
        throw new ConfigurationException(msg, e)
      }
    }
  }


}

import org.orbroker.adapt.MetadataAdapter

private[orbroker]object SQLStatement {
  
  val EOL = scala.compat.Platform.EOL

  def parseSQL(id: Symbol, sql: Seq[String], trim: Boolean, adapter: BrokerAdapter): ParsedSQL = {
    val parsed = SQLParser.parse(sql, trim)
    new ParsedSQL(id, parsed.sql.toString, parsed.params, adapter)
  }
  
  implicit def stringWriterToStringSeq(writer: java.io.StringWriter): Seq[String] = {
    writer.getBuffer.toString.split("[\r\n]").filter(_.trim.length > 0).toSeq
  }

  def toJavaMap(map: Map[String, Any]): JMap[String, Any] = {
    if (map.isEmpty) return java.util.Collections.emptyMap[String, Any]

    val jMap = new JHashMap[String, Any]
    for ((key, value) ← map)
      jMap.put(key, value)
    jMap
  }

  def isVelocity(sql: String) = hasVelocityConditionals(sql) || (sql.contains("$") && isVelocityAvailable)

  def isFreeMarker(sql: String) = hasFreeMarkerConditionals(sql) || (sql.contains("${") && isFreeMarkerAvailable)

  def isProcedureCall(sql: String) = {
    val upper = sql.trim.toUpperCase
    (upper startsWith "CALL") ||
      (upper startsWith "{") ||
      (upper startsWith "EXEC") ||
      ((upper startsWith "BEGIN") && (upper contains "END"))
  }

  private def hasVelocityConditionals(sql: String) =
    ((sql contains "#if") || (sql contains "#foreach")) &&
      (sql contains "#end")

  private def hasFreeMarkerConditionals(sq1: String) =
    ((sq1 contains "#if") && (sq1 contains "/#if")) ||
      ((sq1 contains "#list") && (sq1 contains "/#list"))

  private val isVelocityAvailable = try {
    Class.forName("org.apache.velocity.app.VelocityEngine")
    true
  } catch {
    case _ ⇒ false
  }

  private val isFreeMarkerAvailable = try {
    Class.forName("freemarker.template.Template")
    true
  } catch {
    case _ ⇒ false
  }

}